package org.wjk.asspect;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.wjk.annation.CachaFlush;
import org.wjk.annation.CachaUsing;
import org.wjk.utils.JedisUtils;
import org.wjk.utils.ObjSerializeUtils;

import java.util.Arrays;

/*这是缓存的切面类*/
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class CachaAspect {

    /*
     * 通知
     * @Around()环绕通知 控制目标方法的执行
     * @Before()前置通知 描述的方法是目标方法执行前执行
     * @After()后置通知 描述方法在目标方法后执行
     * @AfterReturning方法在目标方法成功后执行
     * @AfterThrowing方法在目标方法抛出后执行
     * 通知注解的参数： 切入点表达式：
     * bean表达式bean（”要拦截对象在springioc中的名字“）
     * within表达式：within("要拦截类的全类名")
     * excutor表达式：execute（returnType packageName.className.methodName([paramList])）
     * annation表达式：@annation("自定义注解的名")
     * */
    private final JedisUtils jedisUtils;

    @Around("@annotation(c)")
    public Object getDataFromRedis(ProceedingJoinPoint jp, CachaUsing c) {
        /*作用：拦截目标方法，如果redis里目标方法查询数据库数据那么不执行目标方法，直接从redis里获取数据即可
         * 如果redis中没有目标方法查询数据库获取的数据，那么首先执行目标方法，并将目标方法的查询结果缓存到redis中*/
        /*第一步构建保存到数据库中的key值，方法注解中key属性值+目标方法的参数值，两者之间用::间隔*/
        String key = c.key() + "::" + Arrays.toString(jp.getArgs());
        log.debug("key is {}", key);
        //第二部判断redis是否存在指定的key
        if (jedisUtils.exitKey(key)) {
            //如果存在直接从redis获取对应的value，并将其返回
            log.debug("redis里有{}对应的数据，目标方法未执行", key);
            return ObjSerializeUtils.deserialize(jedisUtils.get(key.getBytes()));
        } else {
            //如果不存在执行目标方法，将返回值保存redis中，返回目标方法的返回值
            try {
                Object res = jp.proceed();
                /*String,redis还支持二进制的字节数组*/
                jedisUtils.set(key.getBytes(), ObjSerializeUtils.serialize(res));
                return res;
            } catch (Throwable e) {
                log.debug("切入点方法CachaAspect::getDataFromRedis（）执行目标方法时抛出异常，具体信息为:{}", e.getMessage());
                throw new RuntimeException(e);
            }
        }
    }

    @AfterReturning("@annotation(c)")
    public void flushCache(JoinPoint jp, CachaFlush c){
        String key=c.key();
        key="+"+key+"*";

        jedisUtils.deleteKes(key);
    }

}
